var COSA = COSA || {};

COSA.Core = COSA.Core || {};
/**
 * @memberOf COSA
 * @namespace
 */
COSA.Const = COSA.Const || {};
/**
 * @memberOf COSA
 * @namespace
 */
COSA.Response = COSA.Response || {};
/**
 * @memberOf COSA
 * @namespace
 */
COSA.Request = COSA.Request || {};
COSA.Util = COSA.Util || {};
COSA.Xml = COSA.Xml || {};


(function ( exports ) {
    var _profile = {};

    var _get_xhr = function () {
        return new XMLHttpRequest();
    };

    //Test
    var _get_context = function () {
        var context;
        if (typeof window.GetGlobalContext != "undefined") {
            context = window.GetGlobalContext();
        }
        else {
            if (typeof Xrm != "undefined") {
                context = Xrm.Page.context;
            }
            else if (typeof window.parent.Xrm != "undefined") {
                context = window.parent.Xrm.Page.context;
            }
            else {
                throw new Error("Context is not available.");
            }
        }
        return context;
    };

    //Test
    var _get_orgServicePath = function () {
        return _get_clientUrl() + '/XRMServices/2011/Organization.svc/web';
    };

    var _get_clientUrl = function () {
        var context = _get_context();
        var serverUrl = typeof context.getClientUrl != 'undefined' ? context.getClientUrl() : context.getServerUrl();
        if (serverUrl.match(/\/$/)) {
            serverUrl = serverUrl.substring(0, serverUrl.length - 1);
        }
        return serverUrl;
    };

    var _stringifyRequest = function ( requestObj ) {
        var resString = '';
        if ( Array.isArray(requestObj) ) {
            var sb = new COSA.Util.StringBuilder();
            requestObj.forEach(function ( req ) {
                var tmp = _stringifyNodes(req);
                sb.append(tmp);
            });
        } else {
            resString = _stringifyNodes(requestObj);
        }
        return resString;
    };

    var _stringifyNodes = function ( obj ) {
        var sb = new COSA.Util.StringBuilder();
        sb.concatAndAppend('<', obj.tag);
        for ( var attrName in obj.attr ) {
            sb.concatAndAppend(' ', attrName, '="', obj.attr[attrName], '"');
        }
        if ( (obj.hasOwnProperty('val') && obj.val !== null) || obj.hasOwnProperty('nodes') ) {
            sb.append('>');
            if ( obj.hasOwnProperty('nodes') ) {
                obj.nodes.forEach(function (node) {
                    var str = _stringifyNodes(node);
                    sb.append(str);
                });
            }
            if ( obj.hasOwnProperty('val') ) {
                if (typeof obj.val != 'string') {
                    sb.append(obj.val.toString());
                }
                else {
                    sb.append(obj.val);
                }
            }
            sb.concatAndAppend('</', obj.tag, '>');
        } else {
            sb.append(' />');
        }

        return sb.toString();
    };

    function SoapHeader( requestType, rqBody ) {
        return {
            tag: 'soap:Envelope',
            attr: {'xmlns:soap': 'http://schemas.xmlsoap.org/soap/envelope/'},
            nodes: [
                {
                    tag: 'soap:Body',
                    nodes: [{
                        tag: requestType,
                        attr: {
                            'xmlns': 'http://schemas.microsoft.com/xrm/2011/Contracts/Services',
                            'xmlns:i': 'http://www.w3.org/2001/XMLSchema-instance'
                        },
                        nodes: Array.isArray( rqBody ) ? rqBody : [rqBody]
                    }]
                }
            ]
        };
    }

    var _performRequest = function ( requestBodyStr, internalCallback ) {
        var xhr = _get_xhr();
        xhr.open('POST', _get_orgServicePath(), true);
        xhr.setRequestHeader('Accept', 'application/xml, text/xml, */*');
        xhr.setRequestHeader('Content-Type', 'text/xml; charset=utf-8');
        xhr.setRequestHeader('SOAPAction', 'http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/Execute');

        xhr.onreadystatechange = function () {
            if ( xhr.readyState === 4 ) {
                xhr.onreadystatechange = null;
                internalCallback.apply(this, [ _handleResponse( xhr.responseXML, xhr.responseText ) ]);
            }
        };

        xhr.send(requestBodyStr);
    };

    var _handleResponse = function ( responseXml, responseText ) {
        var xmlDoc;

        if ( responseXml === null || typeof responseXml === 'undefined' || responseXml.xml === null || responseXml.xml === "" ) {
            if ( responseText !== null && responseText !== "" ) {
                throw new Error(responseText);
            } else {
                throw new Error("No response received from the server.");
            }
        }

        xmlDoc = typeof responseXml != 'undefined' ? responseXml : _parseXmlString(responseText);

        return xmlDoc;
    };

    var _parseXmlString = function ( xmlText ) {
        var xmlDoc = null;
        try {
            if ( window.DOMParser ) {
                var parser = new DOMParser();
                xmlDoc = parser.parseFromString(xmlText, 'text/xml');
            } else {
                xmlDoc = new ActiveXObjext('Microsoft.XMLDOM');
                xmlDoc.async = false;
                xmlDoc.loadXML(xmlText);
            }
        } catch ( e ) {
            throw new Error('Can\'t parse');
        }

        return xmlDoc;
    };

    exports.Init = function ( profile ) {
        if ( typeof profile != 'undefined' ) {
            _profile = profile;
        }
        else {
            _profile = {
                isDeepValidate: false
            };
        }
    };

    exports.Execute = function ( request, callback ) {
        var serializationContext = new COSA.SerializationContext(),
            requestXml, requestXmlString;

        try {
            if ( _profile.isDeepValidate ) {
                request.validate();
            }

            requestXml = new SoapHeader('Execute', (new COSA.Request.RequestWrapper(request)).serialize(serializationContext));
            requestXmlString = _stringifyRequest( requestXml );
        }
        catch ( e ) {
            console.error(e);
        }

        _performRequest(requestXmlString, function ( responseXml ) {
            var rsp;

            try {
                rsp = request.deserialize(new COSA.Xml.ExplorableNode(responseXml).node('Envelope.Body'));

                if ( typeof callback == 'function' ) {
                    callback.call(this, rsp);
                }
            }
            catch ( e ) {
                console.error(e);
            }
        });
    };

})(COSA.Core);

(function ( exports ) {
    var constNamespace = {
        Error: {
            INVALID_ATTRIBUTE_TYPE: 'Invalid attribute type!',
            INCORRECT_ATTRIBUTES_COUNT: 'Incorrect attributes count!',
            REQUIRED_ATTRIBUTES_MISSED: 'Some of the required attributes missed',
            SCONTEXT_UNDEF_WRONGTYPE: 'Passed serialization context variable is undefined or not of COSA.SerializationContext type',
            RQPARAM_UNDEFINED: 'Required parameter {0} is undefined',
            RQPARAM_NULL: 'Required parameter {0} is null',
            RQPARAM_NOTINSTANCE: 'Required parameter {0} is not an instance of required type',
            SC_UNDEFINED: '{0}: Serialization Context, passed as a parameter, is \'undefined\'',
            SC_NOLETTER: '{0}: There is no any letter registered in serialization context for provided namespace ({1})',
            SC_ALREADYRESERVED: 'Serialization Context: Letter \'{0}\' already reserved. Override denied!',
            SC_NSALREADYREGISTERED: 'Serialization Context: You try to register already registered namespace. Denied!',
            CS_PASSEDVALUENOTOBJ: 'Serialization Context: Can\'t extract namespace defs. Passed value is not an object.',
            ENC_CANTENCODEGUID: 'Encoder: Can\'t encode GUID: {0}',
            VLD_NOTGUID: 'Validation: \'{0}\' has invalid format for GUID value;',
            ENODE_CANTFINDNODE: 'Deserialization: can\'t find node \'{0}\''
        },
        Message: {
            Retrieve: 'Retrieve',
            RetrieveMultiple: 'RetrieveMultiple',
            Create: 'Create',
            Update: 'Update',
            Delete: 'Delete',
            ExecuteMultiple: 'ExecuteMultiple',
            RetrieveAttribute: 'RetrieveAttribute',
            RetrieveEntity: 'RetrieveEntity',
            RetrieveAllEntities: 'RetrieveAllEntities',
            Associate: 'Associate',
            Disassociate: 'Disassociate',
            ExecuteWorkflow: 'ExecuteWorkflow',
            Assign: 'Assign',
            SetState: 'SetState',
            RetrieveUserPrivileges: 'RetrieveUserPrivileges',
            RetrievePrincipalAccess: 'RetrievePrincipalAccess',
            LoseOpportunity: 'LoseOpportunity',
            WinOpportunity: 'WinOpportunity',
            QualifyLead: 'QualifyLead',
            AddListMembersList: 'AddListMembersList',
            AddMemberList: 'AddMemberList',
            CopyMembersList: 'CopyMembersList',
            RemoveMemberList: 'RemoveMemberList',
            RetrieveMetadataChanges: 'RetrieveMetadataChanges'
        },
        /**
         * @memberOf COSA.Const
         * @enum {string}
         */
        AttributeType: {
            String: 'string',
            WholeNumber: 'int',
            FloatingPointNumber: 'double',
            Decimal: 'decimal',
            DateTime: 'dateTime',
            TwoOption: 'boolean',
            Image: 'base64Binary',
            OptionSet: 'OptionSet',
            Bool: 'boolean',
            Currency: 'Money',
            EntityReference: 'EntityReference',
            ActivityParty: 'EntityCollection',
            EntityCollection: 'EntityCollection',
            Entity: 'BusinessEntity',
            GUID: 'guid',
            Number: 'number',
            EntityFilters: 'EntityFilters'
        },
        /**
         * @memberOf COSA.Const
         * @enum {string}
         */
        FilterOperator: {
            AND: 'And',
            OR: 'Or'
        },
        /**
         * @memberOf COSA.Const
         * @enum {string}
         */
        JoinOperator: {
            Inner: 'Inner',
            LeftOuter: 'LeftOuter',
            Natural: 'Natural'
        },
        /**
         * @memberOf COSA.Const
         * @enum {string}
         */
        OrderType: {
            Asc: 'Ascending',
            Desc: 'Descending'
        },
        /**
         * @memberOf COSA.Const
         * @enum {string}
         */
        ConditionOperator: {
            Like: 'Like',
            BeginsWith: 'BeginsWith',
            Between: 'Between',
            ChildOf: 'ChildOf',
            Contains: 'Contains',
            DoesNotBeginWith: 'DoesNotBeginWith',
            DoesNotContain: 'DoesNotContain',
            DoesNotEndWith: 'DoesNotEndWith',
            Equal: 'Equal',
            EqualBusinessId: 'EqualBusinessId',
            EqualUserId: 'EqualUserId',
            EqualUserLanguage: 'EqualUserLanguage',
            EqualUserOrUserTeams: 'EqualUserOrUserTeams',
            EqualUserTeams: 'EqualUserTeams',
            EndsWith: 'EndsWith',
            InOrAfterFiscalPeriodAndYear: 'InOrAfterFiscalPeriodAndYear',
            InOrBeforeFiscalPeriodAndYear: 'InOrBeforeFiscalPeriodAndYear',
            In: 'In',
            InFiscalPeriod: 'InFiscalPeriod',
            InFiscalPeriodAndYear: 'InFiscalPeriodAndYear',
            InFiscalYear: 'InFiscalYear',
            Last7Days: 'Last7Days',
            LastMonth: 'LastMonth',
            LastWeek: 'LastWeek',
            LastXDays: 'LastXDays',
            LastXFiscalPeriods: 'LastXFiscalPeriods',
            LastXFiscalYears: 'LastXFiscalYears',
            LastXHours: 'LastXHours',
            LastXMonths: 'LastXMonths',
            LastXWeeks: 'LastXWeeks',
            LastXYears: 'LastXYears',
            LastYear: 'LastYear',
            LessEqual: 'LessEqual',
            LessThan: 'LessThan',
            Mask: 'Mask',
            MasksSelect: 'MasksSelect',
            Next7Days: 'Next7Days',
            NextFiscalPeriod: 'NextFiscalPeriod',
            NextFiscalYear: 'NextFiscalYear',
            NextMonth: 'NextMonth',
            NextWeek: 'NextWeek',
            NextXDays: 'NextXDays',
            NextXFiscalPeriods: 'NextXFiscalPeriods',
            NextXFiscalYears: 'NextXFiscalYears',
            NextXHours: 'NextXHours',
            NextXMonths: 'NextXMonths',
            NextXWeeks: 'NextXWeeks',
            NextXYears: 'NextXYears',
            NextYear: 'NextYear',
            NotBetween: 'NotBetween',
            NotEqual: 'NotEqual',
            NotEqualBusinessId: 'NotEqualBusinessId',
            NotEqualUserId: 'NotEqualUserId',
            NotIn: 'NotIn',
            NotLike: 'NotLike',
            NotNull: 'NotNull',
            NotOn: 'NotOn',
            Null: 'Null',
            OlderThanXMonths: 'OlderThanXMonths',
            On: 'On',
            OnOrAfter: 'OnOrAfter',
            OnOrBefore: 'OnOrBefore',
            ThisFiscalPeriod: 'ThisFiscalPeriod',
            ThisFiscalYear: 'ThisFiscalYear',
            ThisMonth: 'ThisMonth',
            ThisWeek: 'ThisWeek',
            ThisYear: 'ThisYear',
            Today: 'Today',
            Tomorrow: 'Tomorrow',
            Yesterday: 'Yesterday',
            GreaterEqual: 'GreaterEqual',
            GreaterThan: 'GreaterThan'
        },
        /**
         * @memberOf COSA.Const
         * @enum {string}
         */
        EntityFilters: {
            Entity: 'Entity',
            Attributes: 'Attributes',
            All: 'Entity Attributes Privileges Relationships',
            Default: 'Entity',
            Privileges: 'Privileges',
            Relationships: 'Relationships'
        },
        /**
         * @memberOf COSA.Const
         * @enum {number}
         */
        KeyValuePairType: {
            StringAnyType: 0,
            StringString: 1,
            StringAnyTypeWithoutPrefix: 2
        },
        /**
         * @memberOf COSA.Const
         * @enum {number}
         */
        EntityRole: {
            Referenced: 1,
            Referencing: 0
        },
        /**
         * @memberOf COSA.Const
         * @enum {string}
         */
        MetadataConditionOperator: {
            Equals: 'Equals',
            GreaterThan: 'GreaterThan',
            In: 'In',
            LessThan: 'LessThan',
            NotEquals: 'NotEquals',
            NotIn: 'NotIn'
        },
        /**
         * @memberOf COSA.Const
         * @enum {string}
         */
        DeletedMetadataFilters: {
            All: 'All',
            Attribute: 'Attribute',
            Default: 'Default',
            Entity: 'Entity',
            Label: 'Label',
            OptionSet: 'OptionSet',
            Relationship: 'Relationship'
        },
        AttributeTypeCode: {
            Boolean:'Boolean',
            Customer:'Customer',
            DateTime:'DateTime',
            Decimal:'Decimal',
            Double:'Double',
            Integer:'Integer',
            Lookup:'Lookup',
            Memo:'Memo',
            Money:'Money',
            Owner:'Owner',
            PartyList:'PartyList',
            Picklist:'Picklist',
            State:'State',
            Status:'Status',
            String:'String',
            Uniqueidentifier:'Uniqueidentifier',
            CalendarRules:'CalendarRules',
            Virtual:'Virtual',
            BigIng:'BigInt',
            ManagedProperty:'ManagedProperty',
            EntityName:'EntityName'
        },
        /**
         * @memberOf COSA.Const
         * @enum {string}
         */
        AttributeRequiredLevel: {
            ApplicationRequired:'ApplicationRequired',
            None:'None',
            Recommended:'Recommended',
            SystemRequired:'SystemRequired'
        },
        /**
         * @memberOf COSA.Const
         * @enum {string}
         */
        DateTimeFormat: {
            DateAndTime: 'DateAndTime',
            DateOnly: 'DateOnly'
        },
        /**
         * @memberOf COSA.Const
         * @enum {string}
         */
        ImeMode: {
            Active:'Active',
            Auto:'Auto',
            Disabled:'Disabled',
            Inactive:'Inactive'
        },
        /**
         * @memberOf COSA.Const
         * @enum {string}
         */
        IntegerFormat: {
            Duration:'Duration',
            Language:'Language',
            Locale:'Locale',
            None:'None',
            TimeZone:'TimeZone'
        },
        /**
         * @memberOf COSA.Const
         * @enum {string}
         */
        OwnershipType: {
            BusinessOwned:'BusinessOwned',
            BusinessParented:'BusinessParented',
            None:'None',
            OrganizationOwned:'OrganizationOwned',
            TeamOwned:'TeamOwned',
            UserOwned:'UserOwned'
        },
        /**
         * @memberOf COSA.Const
         * @enum {string}
         */
        RelationshipType: {
            Default:'Default',
            ManyToManyRelationship:'ManyToManyRelationship',
            OneToManyRelationship:'OneToManyRelationship'
        },
        /**
         * @memberOf COSA.Const
         * @enum {string}
         */
        StringFormat: {
            Email:'Email',
            Phone:'Phone',
            PhoneticGuide:'PhoneticGuide',
            Text:'Text',
            TextArea:'TextArea',
            TickerSymbol:'TickerSymbol',
            Url:'Url',
            VersionNumber:'VersionNumber'
        }
    };

    for ( var d in constNamespace ) {
        if ( constNamespace.hasOwnProperty(d) ) {
            exports[d] = constNamespace[d];
        }
    }

})(COSA.Const);
COSA.Util = {};
(function ( exports ) {

    //region StringBuilder

    function StringBuilder() {
        var _str = [];

        var append = function (str) {
            _str.push(str);
        };

        var concatAndAppend = function () {
            for (var i = 0, len = arguments.length; i < len; i++) {
                _str.push(arguments[i]);
            }
        };

        var toString = function () {
            return _str.join('');
        };

        var clear = function () {
            _str = [];
        };

        return {
            append: append,
            toString: toString,
            clear: clear,
            concatAndAppend: concatAndAppend
        };
    }

    //endregion StringBuilder

    //region StrBuilder

    function StrBuilder () {
        this.str = [];
    }

    StrBuilder.prototype = {
        append: function ( str ) {
            this.str.push(str);
            return this;
        },
        cappend: function () {
            for ( var i = 0, len = arguments.length; i < len; i++ ) {
                this.str.push(arguments[i]);
            }
            return this;
        },
        toString: function () {
            return this.str.join('');
        },
        clear: function () {
            this.str = [];
            return this;
        }
    };

    //endregion StrBuilder

    //region StringFormat

    function StringFormat() {
        return _toFormattedString(false, arguments)
    }

    var _toFormattedString = function (l, values) {
        var resultString = "",
            template = values[0];

        for (var i = 0; true;) {
            var startBracket = template.indexOf("{", i),
                endBracket = template.indexOf("}", i);

            if (startBracket < 0 && endBracket < 0) {
                resultString += template.slice(i);
                break
            }
            if (endBracket > 0 && (endBracket < startBracket || startBracket < 0)) {
                resultString += template.slice(i, endBracket + 1);
                i = endBracket + 2;
                continue
            }
            resultString += template.slice(i, startBracket);
            i = startBracket + 1;
            if (template.charAt(i) === "{") {
                resultString += "{";
                i++;
                continue
            }
            if (endBracket < 0)
                break;
            var h = template.substring(i, endBracket),
                g = h.indexOf(":"),
                k = parseInt(g < 0 ? h : h.substring(0, g), 10) + 1,
                a = g < 0 ? "" : h.substring(g + 1),
                b = values[k];

            if (typeof b === "undefined" || b === null)
                b = "";
            if (b.toFormattedString)
                resultString += b.toFormattedString(a);
            else if (l && b.localeFormat)
                resultString += b.localeFormat(a);
            else if (b.format)
                resultString += b.format(a);
            else
                resultString += b.toString();

            i = endBracket + 1
        }
        return resultString
    };

    //endregion StringFormat

    //region deriveProto

    function derivePrototype ( base, sub ) {
        sub.prototype = Object.create(base.prototype);
        sub.prototype.constructor = sub;
    }

    //endregion deriveProto

    //region deriveOverride

    function deriveOverride ( base, sub, overrides ) {
        derivePrototype(base, sub);
        if ( typeof overrides != 'undefined' ) {
            for ( var method in overrides ) {
                sub.prototype[method] = overrides[method];
            }

            if ( overrides.hasOwnProperty('namespace') ) {
                sub.prototype.namespaceHash = getHashString(overrides.namespace);
            }
        }
    }

    //endregion deriveOverride

    //region getHashString

    function getHashString ( str ) {
        var hash = 0, i, chr, len;
        if ( str.length > 0 ) {
            for (i = 0, len = str.length; i < len; i++) {
                chr   = str.charCodeAt(i);
                hash  = ((hash << 5) - hash) + chr;
                hash |= 0;
            }
        }
        return hash.toString();
    }

    //endregion getHashString

    //region getKeys

    function getKeys ( obj ) {
        var res = [];
        for ( var pr in obj ) {
            res.push(pr);
        }
        return res;
    }

    //endregion getKeys

    //region mergeArrays



    //endregion mergeArrays

    //region formatString

    function formatString ( inputString ) {
        var re =  /\{\d+\}/g,
            er, res = inputString;

        for ( var i = 1; i < arguments.length; i++ ) {
            er = re.exec(inputString);
            if ( er !== null ) {
                res = res.replace(er[0], arguments[i]);
            }
        }
        return res;
    }

    //endregion formatString

    //region formatError

    function formatError ( errorName ) {
        var argArr, res;
        if ( COSA.Const.Error.hasOwnProperty(errorName) ) {
            argArr = [COSA.Const.Error[errorName]];
            for ( var i = 1, len = arguments.length; i < len; i++ ) {
                argArr.push(arguments[i]);
            }
            res = formatString.apply(this, argArr);
        }
        return res;
    }

    //endregion formatError

    exports.StringBuilder = StringBuilder;
    exports.StrBuilder = StrBuilder;
    exports.StringFormat = StringFormat;
    exports.derivePrototype = derivePrototype;
    exports.deriveOverride = deriveOverride;
    exports.getHashString = getHashString;
    exports.getKeys = getKeys;
    exports.formatString = formatString;
    exports.formatError = formatError;

})(COSA.Util);

(function ( exports ) {

    //region privates

    var _separator = '.';

    function _splitSelector ( selector ) {
        var res = null;
        if ( typeof selector != 'undefined' && selector != null ) {
            res = (selector.split(_separator)).map(function ( item ) {
                return item.trim();
            });
        }

        return res.reverse();
    }

    function _findNodes( node, selectors ) {
        var currentSelector = selectors.pop(),
            matchItems = [];

        if ( selectors.length === 0 ) {
            matchItems = _getNodes(node, currentSelector);
        }
        else {
            matchItems = matchItems.concat(_findNodes(_getFirstMatch(node, currentSelector), selectors));
        }

        return matchItems;
    }

    function _getNodes ( node, selector ) {
        var rtrn = [];
        for ( var i = 0, len = node.childNodes.length; i < len; i++ ) {
            if ( node.childNodes[i].localName == selector ) {
                rtrn.push(new ExplorableNode(node.childNodes[i]));
            }
        }
        return rtrn;
    }

    function _processChildNodes ( root, processor ) {
        for ( var i = 0, len = root.childNodes.length; i < len; i++ ) {
            processor.call(this, root.childNodes[i]);
        }
    }

    function _findNode ( node, selectors ) {
        var currSlctr = selectors.pop(),
            item, tmp;

        if ( selectors.length === 0 ) {
            tmp = _getFirstMatch(node, currSlctr);

            if ( typeof tmp != 'undefined' ) {
                item = new ExplorableNode(tmp);
            }
        }
        else {
            tmp = _getFirstMatch(node, currSlctr);

            if ( typeof tmp != 'undefined' ) {
                item = _findNode(tmp, selectors);
            }
            else {
                throw new Error(COSA.Util.formatError('ENODE_CANTFINDNODE', currSlctr));
            }
        }

        return item;
    }

    function _getFirstMatch ( parent, localName ) {
        var res;
        for ( var i = 0, len = parent.childNodes.length; i < len; i++ ) {
            if ( parent.childNodes[i].localName == localName ) {
                res = parent.childNodes[i];
                break;
            }
        }

        return res;
    }

    function _getKeyValue ( node ) {
        var key = _getFirstMatch(node, 'key'),
            value = _getFirstMatch(node, 'value');

        return { key: key.textContent, value: value };
    }

    var _findNodes2 = function (node, level, selectorArr) {
        var matchItems = [];
        for (var i = 0, len = node.childNodes.length; i < len; i++) {
            if (node.childNodes[i].tagName.toLowerCase() == selectorArr[level]) {
                matchItems.push(node.childNodes[i]);
            }
        }
        var result = [];
        if (level < selectorArr.length - 1) {
            for (i = 0, len = matchItems.length; i < len; i++) {
                Array.prototype.push.apply(result, _findNodes(matchItems[i], ++level, selectorArr));
            }
        }
        else {
            for (i = 0, len = matchItems.length; i < len; i++) {
                result.push(new ExplorableNode(matchItems[i]));
            }
            return result;
        }
        return result;
    };

    //endregion privates

    //region ENode

    function ExplorableNode ( root ) {
        this.root = root;
    }

    ExplorableNode.prototype = {
        node: function ( slctr ) {
            return _findNode(this.root, _splitSelector(slctr));
        },
        nodes: function ( slctr ) {
            var splitSlctr, res;
            if ( typeof slctr != 'undefined' ) {
                splitSlctr = _splitSelector(slctr);
                if ( splitSlctr.length === 1 ) {
                    res = _getNodes(this.root, splitSlctr[0]);

                }
                else {
                    res = _findNodes(this.root, splitSlctr);
                }
            }
            else {
                res = [];
                _processChildNodes(this.root, function ( child ) {
                    res.push(new ExplorableNode(child));
                });
            }

            return res;
        },
        namedNodes: function () {
            var rs;

            if ( this.root.childNodes.length > 0 ) {
                rs = {};
                _processChildNodes(this.root, function ( child ) {
                    rs[child.localName] = new ExplorableNode(child);
                });
            }
            return rs;
        },
        getDouble: function () {
            return parseFloat(this.root.textContent);
        },
        attr: function ( attrName ) {
            var namedItem, res;

            if ( typeof attrName != 'undefined' ) {
                namedItem = this.root.attributes.getNamedItem(attrName);
                if ( namedItem !== null ) {
                    res = namedItem.value;
                }
            }

            return res;
        },
        keyValueCollection: function () {
            var rs, tmp;

            if ( this.root.childNodes.length > 0 ) {
                rs = {};
                for ( var i = 0, len = this.root.childNodes.length; i < len; i++ ) {
                    tmp = _getKeyValue(this.root.childNodes[i]);
                    rs[tmp.key] = new ExplorableNode(tmp.value);
                }
            }

            return rs;
        },
        text: function () {
            return this.root.textContent;
        },
        toInt: function () {
            return parseInt(this.root.textContent);
        },
        toDouble: function () {
            return parseFloat(this.root.textContent);
        },
        toBoolean: function () {
            return this.root.textContent == 'true';
        },
        toEntityReference: function () {
            var n = this.namedNodes();
            return new COSA.EntityReference(n.LogicalName.text(), n.Id.text());
        },
        toDateTime: function () {
            return new Date(this.root.textContent);
        }
    };

    //endregion ENode

    //region TypedNodeMapper

    function TypedNodeMapper ( context ) {
        this.context = context;
        this.map = null;
    }

    TypedNodeMapper.prototype = {
        applyValues: function ( values ) {
            var converter, prts;

            if ( typeof values == 'undefined' || values === null ) {
                this.context = null;
            }
            else {
                converter = new StringConverter();
                for ( var prop in this.map ) {
                    prts = this.map[prop].split(':');
                    if ( values.hasOwnProperty(prts[1])) {
                        this.context[prop] =
                            converter[prts[0]]
                                .call(this, values[prts[1]]);
                    }
                }
            }
        },
        appVal: function ( values ) {
            var cnvrtr = new StringConverter(), s;
            for ( var prp in this.map ) {
                this.map[prp].forEach(function ( nodeName ) {
                    this.context[nodeName] = (cnvrtr[prp].bind(values[nodeName]))();
                });
            }
        },
        useMap: function ( map ) {
            this.map = map;
            return this;
        }
    };

    //endregion TypedNodeMapper

    function intCnvrtr () {
        return parseInt(this.text());
    }

    //region Parser

    function StringConverter () {
    }

    StringConverter.prototype = {
        string: function ( node ) {
            return node.text();
        },
        int: function ( node ) {
            return parseInt(node.text());
        },
        boolean: function ( node ) {
            return node.text() == 'true';
        },
        double: function ( node ) {
            return parseFloat(node.text());
        },
        localizedLabelSet: function ( node ) {
            return (new COSA.LocalizedLabelSet()).deserialize(node);
        },
        localizedLabelCollection: function ( node ) {
            return (new COSA.LocalizedLabelCollection()).deserialize(node);
        },
        localizedLabel: function ( node ) {
            return (new COSA.LocalizedLabel()).deserialize(node);
        },
        managedProperty: function ( node ) {
            return (new COSA.ManagedProperty()).deserialize(node);
        },
        valueNode: function ( node ) {
            return (new COSA.ValueNode()).deserialize(node);
        },
        stringCollection: function ( node ) {
            return (new COSA.StringCollection()).deserialize(node);
        },
        boolOptionSetMetadata: function ( node ) {
            return (new COSA.BoolOptionSetMetadata()).deserialize(node);
        },
        optionSetMetadata: function ( node ) {
            return (new COSA.OptionSetMetadata()).deserialize(node);
        },
        optionMetadata: function ( node ) {
            return (new COSA.OptionMetadata()).deserialize(node);
        },
        optionsCollection: function ( node ) {
            return (new COSA.OptionsCollection()).deserialize(node);
        },
        attributeMetadataCollection: function ( node ) {
            return (new COSA.AttributeMetadataCollection()).deserialize(node);
        },
        privilegesMetadataCollection: function ( node ) {
            return (new COSA.PrivilegesMetadataCollection()).deserialize(node);
        },
        mMRelationshipCollection: function ( node ) {
            return (new COSA.ManyToManyRelationshipCollection()).deserialize(node);
        },
        oMRelationshipCollection: function ( node ) {
            return (new COSA.OneToManyRelationshipCollection()).deserialize(node);
        },
        mORelationshipCollection: function ( node ) {
            return (new COSA.ManyToOneRelationshipCollection()).deserialize(node);
        },
        menuConfiguration: function ( node ) {
            return (new COSA.MenuConfiguration()).deserialize(node);
        },
        cascadeConfiguration: function ( node ) {
            return (new COSA.CascadeConfiguration()).deserialize(node);
        }
    };

    //endregion Parser

    exports.ExplorableNode = ExplorableNode;
    exports.TypedNodeMapper = TypedNodeMapper;

})(COSA.Xml);